package cz.boxdesign.library.entity.dao;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;

import cz.boxdesign.library.common.dao.DatabaseOperationException;
import cz.boxdesign.library.configuration.DatabaseOperation;
import cz.boxdesign.library.entity.LibraryEntity;
import cz.boxdesign.library.entity.dao.query.Query;

/**
 * 
 * @author jlattenberk
 * 
 * @param <T>
 */
public abstract class EntityDaoAbstract<T extends LibraryEntity, Q extends Query> implements EntityDao<T> {

	protected final Log log = LogFactory.getLog(EntityDaoAbstract.class);

	@Autowired
	public DatabaseOperation jdbc;

	@Override
	public List<T> getAll() throws DatabaseOperationException {
		return getAll("");
	}

	@Override
	public T getById(Integer id) throws DatabaseOperationException {
		List<T> result = getAll("and id = " + id);
		if (result.size() == 0) {
			throw new DatabaseOperationException("Incorret entityId. No Entity with id=" + id);
		}
		return result.get(0);
	}

	@Override
	public List<T> getByClause(String clause) throws DatabaseOperationException {
		return getAll(clause);
	}

	@Override
	public void updateEntity(T entity) throws DatabaseOperationException {
		insertOrUpdate(entity, Q.UPDATE);
	}

	@Override
	public void insertEntity(T entity) throws DatabaseOperationException {
		insertOrUpdate(entity, Q.INSERT);
	}

	@Override
	public void deleteEntity(T entity) throws DatabaseOperationException {
		insertOrUpdate(entity, Q.DELETE);
	}

	@Override
	public void batchUpdate(List<T> entityList) throws DatabaseOperationException {
		if (entityList != null) {
			batch(entityList, Q.UPDATE);
		}
	}

	@Override
	public void batchInsert(List<T> entityList) throws DatabaseOperationException {
		if (entityList != null) {
			if (!entityList.isEmpty()) {
				batch(entityList, Q.INSERT);
			}
		}
	}

	@Override
	public void batchDelete(List<T> entityList) throws DatabaseOperationException {
		if (entityList != null) {
			batch(entityList, Q.DELETE);
		}
	}

	/**
	 * 
	 * @param clause
	 * @return
	 * @throws DatabaseOperationException
	 */
	List<T> getAll(String clause) throws DatabaseOperationException {
		List<T> result = null;
		try {
			jdbc.beginTransaction();
			String sql = getSqlQuery(Q.SELECT) + clause;
			log.info(sql);
			ResultSet rs = jdbc.getSatement().executeQuery(sql);
			result = new ArrayList<T>();
			getEntityList(result, rs);
			rs.close();
			jdbc.endTransaction();
		} catch (SQLException e) {
			jdbc.rollback();
			jdbc.reconect();
			log.info("RECONNECT");
			throw new DatabaseOperationException(e.getMessage());
		}
		return result;
	}

	/**
	 * @param entity
	 * @param update
	 * @throws DatabaseOperationException
	 */
	void insertOrUpdate(T entity, int queryValue) throws DatabaseOperationException {
		try {
			jdbc.beginTransaction();
			String sql = getSqlQuery(queryValue);
			log.info(sql);
			PreparedStatement ps = jdbc.getPreparedStatement(sql);
			fillStatement(entity, queryValue, ps);
			ps.executeUpdate();
			ps.close();
			jdbc.endTransaction();
		} catch (SQLException e) {
			e.printStackTrace();
			jdbc.rollback();
			jdbc.reconect();
			log.info("RECONNECT");
			throw new DatabaseOperationException("Insert/Update of entity " + entity.toString() + "failed!");
		}
	}

	/**
	 * @param entityList
	 * @param update
	 * @throws DatabaseOperationException
	 */
	void batch(List<T> entityList, int queryValue) throws DatabaseOperationException {
		try {
			String sql = getSqlQuery(queryValue);
			log.info(sql);
			PreparedStatement ps = jdbc.getPreparedStatement(sql);
			for (T entity : entityList) {
				fillStatement(entity, queryValue, ps);
				ps.addBatch();
			}
			ps.executeBatch();
			ps.close();
			jdbc.endTransaction();
		} catch (SQLException e) {
			jdbc.rollback();
			jdbc.reconect();
			log.info("RECONNECT");
			throw new DatabaseOperationException("Batch operation failed!");
		}
	}

	/**
	 * @param result
	 * @param rs
	 * @throws SQLException
	 */
	abstract void getEntityList(List<T> result, ResultSet rs) throws SQLException, DatabaseOperationException;

	/**
	 * 
	 * @param queryEnum
	 * @return
	 */
	abstract String getSqlQuery(int queryValue);

	/**
	 * @param entity
	 * @param clause
	 * @param ps
	 * @throws SQLException
	 */
	abstract void fillStatement(T entity, int queryValue, PreparedStatement ps) throws SQLException;
}
